#include <stdio.h>
#include <stdbool.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/time.h>
#include <assert.h>
#include <signal.h>
#include <string.h>
#include <pthread.h>

#include "getopt.h"
#include "bb_api.h"

static bool g_run = true;
static void sig_func(int sig) {
	g_run = false;
}

static uint64_t time_ms() {
    struct timeval tv;
    gettimeofday(&tv, NULL);
    return (uint64_t)tv.tv_sec*1000 + tv.tv_usec/1000;
}

static void bb_event_mcs(void* arg, void* usr)
{
    printf("get mcs cb\n");
}

static bb_dev_handle_t *bb_open_ex() {
    bb_host_t* phost = NULL;
    bb_dev_t** devs = NULL;
    bb_dev_handle_t* hdev = NULL;
    // 1.
    // bb_init();

    // 2.
    if (bb_host_connect(&phost, "127.0.0.1", 50000)) {
        printf("failed to connect host!\n");
        return NULL;
    }

    // 3.
    int dev_nr = bb_dev_getlist(phost, &devs);
    if (dev_nr <= 0) {
        printf("get dev list failed or no dev!\n");
        return NULL;
    }

    // 4.
    hdev = bb_dev_open(devs[0]);

    // 5.
    bb_dev_freelist(devs);

    if (!hdev) {
        printf("open dev failed!\n");
        return NULL;
    }

    bb_set_event_callback_t callback;
    callback.event = BB_EVENT_MCS_CHANGE;
    callback.callback = bb_event_mcs;
    callback.user = 0;
    bb_ioctl(hdev, BB_SET_EVENT_SUBSCRIBE, &callback, NULL);

    return hdev;
}


static int datagram_read(int sockfd, char *buf, int size, int timeout) {
	int read = 0;

    while (g_run) {
		int n = bb_socket_read(sockfd, buf+read, size-read, -1);
		if ( n < 0) {
            printf("read error!\n");
            break;  
        }

		read += n;

		if (read < size) {
			continue;
		}

		break;
	}

	return read;
}

typedef struct {
	uint64_t threshold;
	uint64_t stat;
} stats_t;

static void run_ap_ntp(int sockfd, uint64_t packet_size) {
    char buf[8196];
	stats_t stats[5] = {{5, 0}, {10, 0}, {20, 0}, {50, 0}, {-1, 0}};
	uint64_t total = 0;
	uint64_t begin = 0;
    uint64_t max_diff = 0;
    uint64_t min_diff = -1;

    while (g_run) {
		if (datagram_read(sockfd, buf, packet_size, -1) != packet_size) {
            break;  
        }

		uint64_t rcv_ms = time_ms();
		uint64_t snd_ms = 0;
		memcpy(&snd_ms, buf+8, sizeof(snd_ms));
		uint64_t diff = rcv_ms - snd_ms;
		for (int i=0; i<sizeof(stats)/sizeof(stats[0]); ++i) {
			if (diff <= stats[i].threshold) {
				++stats[i].stat;
				break;
			}
		}
		++ total;

        if (begin == 0) {
            begin = rcv_ms;
        }
        if (max_diff < diff) {
            max_diff = diff;
        }
        if (min_diff > diff) {
            min_diff = diff;
        }
    	//buf[read] = 0;
    	//printf("[%llu]%s\n", time_ms(), buf);
	}
    
	if (total > 0) {
        printf("\n");
        printf("\n");
        printf("\n");
        printf("begin:%llu, max delay:%llu, min delay:%llu\n", begin, max_diff, min_diff);
        printf("---------------------------------------------------\n");
		printf("5ms: %.0f\%, 10ms: %.0f\%, 20ms: %.0f\%, 50ms: %.0f\%, other: %.0f\%, total: %llu\n",
				(double)stats[0].stat/total*100, (double)stats[1].stat/total*100, (double)stats[2].stat/total*100, 
				(double)stats[3].stat/total*100, (double)stats[4].stat/total*100, total);
        //uint64_t total2 = stats[2];
        //if (total2 > 0) {
		//    printf("%.0f\%, %.0f\%, %.0f\%, %.0f\%, %.0f\%, total: %llu\n",
		//    		(double)stats2[0]/total2*100, (double)stats2[1]/total2*100, (double)stats2[2]/total2*100, 
		//    		(double)stats2[3]/total2*100, (double)stats2[4]/total2*100, total2);
        //}
        printf("---------------------------------------------------\n");
        printf("\n");
        printf("\n");
	}

}

static void run_dev_ntp(int sockfd, uint64_t throughput, uint64_t packet_size, uint64_t dur) {
    char buf[8196];
	uint64_t pcnt = throughput * dur / packet_size;
	uint64_t interv = 1000 * packet_size * 1000 / throughput;
	uint64_t begin = time_ms();
    printf("\n");
    printf("\n");
    printf("\n");
    printf("start:\n");
    printf("---------------------------------------------------\n");
    printf("pcnt:%llu, interv:%llu, begin:%llu, dur:%llu\n",
            pcnt, interv, begin, dur);

	for (uint64_t i=0; i<pcnt; ++i) {
		if (!g_run) {
			break;
		}

		uint64_t snd_ms = time_ms();
		memcpy(buf, &i, sizeof(i));
		memcpy(buf+8, &snd_ms, sizeof(snd_ms));
        if (bb_socket_write(sockfd, buf, packet_size, -1) != packet_size) {
            printf("write error!\n");
            break;  
        }

		uint64_t cost = time_ms() - snd_ms;
		if (cost > 10) {
    	    printf("************block: %llu, %llu\n", cost, i);
		}
        //printf("[%llu] %llu\n", snd_ms, i);

		uint64_t st = begin*1000 + (i+1)*interv - time_ms()*1000;
		if (st < ((uint64_t)-1)/2) {
        	usleep(st);
		} else {
    	    //printf("block\n");
        }

        //if ((snd_ms - begin)%1000 <= interv/1000) {
        //    printf(" - ");
        //}
    	//printf("st:%llu\n", st);
	}

	uint64_t end = time_ms();
    printf("\n");
    printf("\n");
    printf("\n");
    printf("end:\n");
    printf("---------------------------------------------------\n");
    printf("[%llu - %llu] %llu\n", begin, end, end-begin);
    printf("---------------------------------------------------\n");
    printf("\n");
    printf("\n");

}

static void run_ap_rtt(int sockfd, uint64_t recv_packet_size, uint64_t send_packet_size) {
    char buf[8196];
	stats_t stats[5] = {{5, 0}, {10, 0}, {20, 0}, {50, 0}, {-1, 0}};
	uint64_t total = 0;
	uint64_t begin = 0;
    uint64_t max_diff = 0;
    uint64_t min_diff = -1;

	FILE *fp = fopen("ap_rtt.txt", "w");

    while (g_run) {
        if (datagram_read(sockfd, buf, recv_packet_size, -1) != recv_packet_size) {
            break;
        }
		uint64_t rcv_ms = time_ms();
		uint64_t snd_ms = 0;
		uint64_t index = 0;

		memcpy(&index, buf, sizeof(index));
		memcpy(&snd_ms, buf+8, sizeof(snd_ms));
		memcpy(buf+16, &rcv_ms, sizeof(rcv_ms));

        if (bb_socket_write(sockfd, buf, send_packet_size, -1) != send_packet_size) {
            printf("write error!\n");
            break;  
        }
        
		//printf("[%llu] %llu %llu\n", rcv_ms, snd_ms, index);
		fprintf(fp, "[%llu] %llu %llu\n", rcv_ms, snd_ms, index);

		uint64_t diff = rcv_ms - snd_ms;
		for (int i=0; i<sizeof(stats)/sizeof(stats[0]); ++i) {
			if (diff <= stats[i].threshold) {
				++stats[i].stat;
				break;
			}
		}
		++ total;

        if (begin == 0) {
            begin = rcv_ms;
        }
        if (max_diff < diff) {
            max_diff = diff;
        }
        if (min_diff > diff) {
            min_diff = diff;
        }
    	//buf[read] = 0;
    	//printf("[%llu]%s\n", time_ms(), buf);
	}

	if (total > 0) {
        printf("\n");
        printf("\n");
        printf("\n");
        printf("begin:%llu, max delay:%llu, min delay:%llu\n", begin, max_diff, min_diff);
        printf("---------------------------------------------------\n");
		printf("5ms: %.0f\%, 10ms: %.0f\%, 20ms: %.0f\%, 50ms: %.0f\%, other: %.0f\%, total: %llu\n",
				(double)stats[0].stat/total*100, (double)stats[1].stat/total*100, (double)stats[2].stat/total*100, 
				(double)stats[3].stat/total*100, (double)stats[4].stat/total*100, total);
        //uint64_t total2 = stats[2];
        //if (total2 > 0) {
		//    printf("%.0f\%, %.0f\%, %.0f\%, %.0f\%, %.0f\%, total: %llu\n",
		//    		(double)stats2[0]/total2*100, (double)stats2[1]/total2*100, (double)stats2[2]/total2*100, 
		//    		(double)stats2[3]/total2*100, (double)stats2[4]/total2*100, total2);
        //}
        printf("---------------------------------------------------\n");
        printf("\n");
        printf("\n");
	}
	fclose(fp);
}

typedef struct {
	int fd;
	uint64_t recv_packet_size;
} thread_args_t;

static void* dev_read_rtt( void* args ) {
    printf("dev_read_rtt begin\n");
    uint64_t recv_packet_size = ((thread_args_t*)args)->recv_packet_size;
    int sockfd = ((thread_args_t*)args)->fd;

    char buf[8196];
	stats_t stats[5] = {{5, 0}, {10, 0}, {20, 0}, {50, 0}, {-1, 0}};
	uint64_t total = 0;
	uint64_t begin = 0;
    uint64_t max_diff = 0;
    uint64_t min_diff = -1;

	FILE *fp = fopen("dev_rtt.txt", "w");

    while (g_run) {
        if (datagram_read(sockfd, buf, recv_packet_size, -1) != recv_packet_size) {
            break;
        }

		uint64_t local_rcv_ms = time_ms();
		uint64_t snd_ms = 0;
		uint64_t remote_rcv_ms = 0;
		uint64_t index = 0;

		memcpy(&index, buf, sizeof(index));
		memcpy(&snd_ms, buf+8, sizeof(snd_ms));
		memcpy(&remote_rcv_ms, buf+16, sizeof(remote_rcv_ms));

		//printf("[%llu] %llu %llu %llu\n", local_rcv_ms, remote_rcv_ms, snd_ms, index);
		fprintf(fp, "[%llu] %llu %llu %llu\n", local_rcv_ms, remote_rcv_ms, snd_ms, index);

		uint64_t diff = local_rcv_ms - snd_ms;
		for (int i=0; i<sizeof(stats)/sizeof(stats[0]); ++i) {
			if (diff <= stats[i].threshold) {
				++stats[i].stat;
				break;
			}
		}
		++ total;

        if (begin == 0) {
            begin = local_rcv_ms;
        }
        if (max_diff < diff) {
            max_diff = diff;
        }
        if (min_diff > diff) {
            min_diff = diff;
        }

        if (remote_rcv_ms < snd_ms || remote_rcv_ms > local_rcv_ms) {
            printf("remote time error, send:%llu, remote recv:%llu, local recv:%llu\n",
                    snd_ms, remote_rcv_ms, local_rcv_ms);
        }
	}

	if (total > 0) {
        printf("\n");
        printf("\n");
        printf("\n");
        printf("begin:%llu, max delay:%llu, min delay:%llu\n", begin, max_diff, min_diff);
        printf("---------------------------------------------------\n");
		printf("5ms: %.0f\%, 10ms: %.0f\%, 20ms: %.0f\%, 50ms: %.0f\%, other: %.0f\%, total: %llu\n",
				(double)stats[0].stat/total*100, (double)stats[1].stat/total*100, (double)stats[2].stat/total*100, 
				(double)stats[3].stat/total*100, (double)stats[4].stat/total*100, total);
        //uint64_t total2 = stats[2];
        //if (total2 > 0) {
		//    printf("%.0f\%, %.0f\%, %.0f\%, %.0f\%, %.0f\%, total: %llu\n",
		//    		(double)stats2[0]/total2*100, (double)stats2[1]/total2*100, (double)stats2[2]/total2*100, 
		//    		(double)stats2[3]/total2*100, (double)stats2[4]/total2*100, total2);
        //}
        printf("---------------------------------------------------\n");
        printf("\n");
        printf("\n");
	}

	fclose(fp);

    printf("dev_read_rtt end\n");

    return NULL;
}

static void run_dev_rtt(int sockfd, uint64_t throughput, uint64_t send_packet_size, uint64_t recv_packet_size, uint64_t dur) {
    pthread_t tid = 0;
	static thread_args_t args;
	args.recv_packet_size = recv_packet_size;
	args.fd = sockfd;
 	int ret = pthread_create(&tid, NULL, dev_read_rtt, (void*)&args);
    if (ret != 0) {
    	printf("create thread error\n");
		return;
    }

	run_dev_ntp(sockfd, throughput, send_packet_size, dur);

	//usleep(1*1000*1000);

	//g_run = false;

	pthread_join(tid, NULL);
}

int main(int argc, char** argv) {
    static const char *short_opts = "hr:d:t:e:s:m:";
    static struct option long_opts[] = {
            {"help", no_argument, 0, 'h'},
            {"role", required_argument, 0, 'r'}, // 0:ap(read), 1:dev(write)
            {"duration", required_argument, 0, 'd'}, // second
            {"throughput", required_argument, 0, 't'}, // k byte per second
            {"recvpacketsize", required_argument, 0, 'e'}, // recv byte
            {"sendpacketsize", required_argument, 0, 's'}, // send byte
            {"mode", required_argument, 0, 'm'}, // 0:ntp, 1:rtt 
    };

    int c = 0;
    int opt_index = 0;

    int role = 0;
    uint64_t dur = 0;
    uint64_t throughput = 0;
    uint64_t recv_packet_size = 0;
    uint64_t send_packet_size = 0;
    int mode = 0;

    do {
        c = getopt_long(argc, argv, short_opts, long_opts, &opt_index);
        if (c == -1) {
            break;
        }

        switch (c) {
        case 'h':
            // todo print help
            break;
        case 'r':
            role = strtoul(optarg, NULL, 10);
        	printf("role :%d\n", role);
            break;
        case 'd':
            dur = strtoul(optarg, NULL, 10);
        	printf("dur :%llu\n", dur);
            break;
        case 't':
            throughput = strtoul(optarg, NULL, 10);
        	printf("throughput :%llu\n", throughput);
            break;
        case 'e':
            recv_packet_size = strtoul(optarg, NULL, 10);
        	printf("recv packet size:%llu\n", recv_packet_size);
			assert(recv_packet_size < 8 * 1024);
			assert(recv_packet_size > 16);
            break;
        case 's':
            send_packet_size = strtoul(optarg, NULL, 10);
        	printf("send packet size:%llu\n", send_packet_size);
			assert(send_packet_size < 8 * 1024);
			assert(send_packet_size > 16);
            break;
        case 'm':
            mode = strtoul(optarg, NULL, 10);
        	printf("mode:%llu\n", mode);
			break;
        default:
            printf(" getopt returned character code 0%o \n", c);
            break;
        }
    } while(true);

    bb_dev_handle_t * hdev = bb_open_ex();
    if (hdev == NULL) {
        return -1;
    }

	signal(SIGINT, sig_func);

    int sockfd = -1;
    sockfd = bb_socket_open(hdev, BB_SLOT_0, 0, BB_SOCK_FLAG_TX | BB_SOCK_FLAG_RX, NULL);
    if (sockfd < 0) {
        printf("create bb socket failed:%d!\n", sockfd);
        return -1;  
    }

    if (mode == 0) { // ntp
        if (role == 0) { // ap
            run_ap_ntp(sockfd, recv_packet_size);
        } else { // dev
            run_dev_ntp(sockfd, throughput, send_packet_size, dur);
        }
    } else { // rtt
        if (role == 0) { // ap
            run_ap_rtt(sockfd, recv_packet_size, send_packet_size);
        } else { // dev
            run_dev_rtt(sockfd, throughput, send_packet_size, recv_packet_size, dur);
        }
    }

    return 0;
}

